/**
 *  Copyright (C) 2021 Huawei Device Co., Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include <jni.h>
#include <string>
#include <rtmp.h>
#include <unistd.h>
#include "rtmp_log.h"

#define HTON16(x)((x>>8&0xff)|(x<<8&0xff00))
#define HTON24(x)((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00))
#define HTON32(x)((x>>24&0xff)|(x>>8&0xff00)|\
(x<<8&0xff0000)|(x<<24&0xff000000))
#define HTONTIME(x)((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00)|(x&0xff000000))

/*read 1 byte*/
int ReadU8(uint32_t * u8, FILE * fp) {
    if (fread(u8, 1, 1, fp) != 1)
        return 0;
    return 1;
}

/*read 2 byte*/
int ReadU16(uint32_t * u16, FILE * fp) {
    if (fread(u16, 2, 1, fp) != 1)
        return 0;
    *u16 = HTON16(*u16);
    return 1;
}

/*read 3 byte*/
int ReadU24(uint32_t * u24, FILE * fp) {
    if (fread(u24, 3, 1, fp) != 1)
        return 0;
    *u24 = HTON24(*u24);
    return 1;
}

/*read 4 byte*/
int ReadU32(uint32_t * u32, FILE * fp) {
    if (fread(u32, 4, 1, fp) != 1)
        return 0;
    *u32 = HTON32(*u32);
    return 1;
}

/*read 1 byte,and loopback 1 byte at once*/
int PeekU8(uint32_t * u8, FILE * fp) {
    if (fread(u8, 1, 1, fp) != 1)
        return 0;
    fseek(fp, -1, SEEK_CUR);
    return 1;
}

/*read 4 byte and convert to time format*/
int ReadTime(uint32_t * utime, FILE * fp) {
    if (fread(utime, 4, 1, fp) != 1)
        return 0;
    *utime = HTONTIME(*utime);
    return 1;
}

/**
 * 检查操作结果
 * @param rtmp
 * @param result，值为0表示成功，非0表示失败
 * @return
 */
bool checkResult(RTMP * rtmp, int result) {
    if (result == 0) {
        //释放RTMP
        RTMP_Free(rtmp);
        return false;
    }
    return true;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_rtmp_librtmp_RtmpUtil_getRtmpVersion(JNIEnv * env, jclass clazz) {
    char version[100];
    sprintf(version, "rtmp version : %d", RTMP_LibVersion());
    return env->NewStringUTF(version);
}

extern "C"
JNIEXPORT jboolean JNICALL Java_com_rtmp_librtmp_RtmpUtil_pushRtmp(JNIEnv * env, jclass clazz, jstring host, jstring path) {
    //分配一个RTMP
    RTMP * rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char * url = env->GetStringUTFChars(host, NULL);
    const char * file = env->GetStringUTFChars(path, NULL);
    int pushResult = 0;
    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *)url);
    if (checkResult(rtmp, result)) {
        //开启写入，在connect之前调用
        RTMP_EnableWrite(rtmp);
        //连接服务器
        result = RTMP_Connect(rtmp, NULL);
        if (checkResult(rtmp, result)) {
            //连接流
            result = RTMP_ConnectStream(rtmp, 0);
            if (checkResult(rtmp, result)) {
                FILE * fp = NULL;
                //打开文件
                fp = fopen(file, "rb");
                if (fp) {
                    RTMPPacket * packet = NULL;
                    //开始时间
                    uint32_t start_time = 0;
                    //当前时间
                    uint32_t now_time = 0;
                    //上一帧时间
                    long pre_frame_time = 0;
                    //最后一次时间
                    long lasttime = 0;
                    //下一帧是否是关键帧
                    int bNextIsKey = 1;
                    uint32_t preTagsize = 0;

                    //包属性
                    uint32_t type = 0;
                    uint32_t datalength = 0;
                    uint32_t timestamp = 0;
                    uint32_t streamid = 0;

                    packet = (RTMPPacket *)malloc(sizeof(RTMPPacket));
                    RTMPPacket_Alloc(packet, 1024 * 64);
                    RTMPPacket_Reset(packet);

                    packet->m_hasAbsTimestamp = 0;
                    packet->m_nChannel = 0x04;
                    packet->m_nInfoField2 = rtmp->m_stream_id;

                    //jump over FLV Header
                    fseek(fp, 9, SEEK_SET);
                    //jump over previousTagSizen
                    fseek(fp, 4, SEEK_CUR);
                    start_time = RTMP_GetTime();
                    while (1) {
                        if ((((now_time = RTMP_GetTime()) - start_time)
                        < (pre_frame_time)) && bNextIsKey) {
                            //wait for 1 sec if the send process is too fast
                            //this mechanism is not very good,need some improvement
                            if (pre_frame_time > lasttime) {
                                lasttime = pre_frame_time;
                            }
                            sleep(1);
                            continue;
                        }

                        //not quite the same as FLV spec
                        if (!ReadU8(&type, fp)) {
                            RTMP_LOG("read u8 fail");
                            break;
                        }
                        if (!ReadU24(&datalength, fp)) {
                            RTMP_LOG("read u24 fail");
                            break;
                        }
                        if (!ReadTime(&timestamp, fp)) {
                            RTMP_LOG("read time fail");
                            break;
                        }
                        if (!ReadU24(&streamid, fp)) {
                            RTMP_LOG("read u24 fail");
                            break;
                        }

                        if (type != 0x08 && type != 0x09) {
                            //跳过非音频和非视频帧，
                            //同时跳过下一个PreviousTagSizen
                            fseek(fp, datalength + 4, SEEK_CUR);
                            continue;
                        }

                        if (fread(packet->m_body, 1, datalength, fp) != datalength) {
                            RTMP_LOG("read body fail");
                            break;
                        }

                        packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
                        packet->m_nTimeStamp = timestamp;
                        packet->m_packetType = type;
                        packet->m_nBodySize = datalength;
                        pre_frame_time = timestamp;

                        result = RTMP_IsConnected(rtmp);
                        if (!checkResult(rtmp, result)) {
                            RTMP_LOG("rtmp is not connected");
                            break;
                        }

                        result = RTMP_SendPacket(rtmp, packet, 0);
                        if (!checkResult(rtmp, result)) {
                            RTMP_LOG("rtmp send packet fail.");
                            break;
                        }

                        if (!ReadU32(&preTagsize, fp)) {
                            RTMP_LOG("rtmp read u32 fail.");
                            break;
                        }

                        if (!PeekU8(&type, fp)) {
                            RTMP_LOG("rtmp peek u8 fail.");
                            break;
                        } else {
                            RTMP_LOG("rtmp peek u8 success.");
                        }
                        if (type == 0x09) {
                            if (fseek(fp, 11, SEEK_CUR) != 0) {
                                RTMP_LOG("rtmp fseek fail.");
                                break;
                            }
                            if (!PeekU8(&type, fp)) {
                                RTMP_LOG("rtmp peek u8(type==0x09) fail.");
                                break;
                            }
                            if (type == 0x17)
                                bNextIsKey = 1;
                            else
                                bNextIsKey = 0;

                            fseek(fp, -11, SEEK_CUR);
                        }
                    }
                    //关闭文件流
                    fclose(fp);
                    RTMPPacket_Free(packet);
                    packet = nullptr;
                    pushResult = 1;
                } else {
                    RTMP_LOG("open file fail.");
                }
                RTMP_Close(rtmp);
                RTMP_LOG("rtmp send finish.");
            } else {
                RTMP_LOG("connect stream fail.");
            }
        } else {
            RTMP_LOG("connect url fail.");
        }
    } else {
        RTMP_LOG("set up  url fail.");
    }
    RTMP_Free(rtmp);
    rtmp = nullptr;

    env->ReleaseStringUTFChars(host, url);
    env->ReleaseStringUTFChars(path, file);
    return pushResult;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_receiveRtmp(JNIEnv * env, jclass clazz, jstring host, jstring path) {

    //分配一个RTMP
    RTMP * rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char * url = env->GetStringUTFChars(host, NULL);
    const char * file = env->GetStringUTFChars(path, NULL);

    int receiveResult = 0;
    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *)url);
    if (checkResult(rtmp, result)) {
        //表示直播流
        rtmp->Link.lFlags |= RTMP_LF_LIVE;
        //一个小时
        RTMP_SetBufferMS(rtmp, 3600 * 1000);
        //连接服务器
        result = RTMP_Connect(rtmp, NULL);
        if (checkResult(rtmp, result)) {
            //连接流
            result = RTMP_ConnectStream(rtmp, 0);
            if (checkResult(rtmp, result)) {
                FILE * fp = NULL;
                //打开文件
                fp = fopen(file, "wb");
                if (fp) {
                    int nRead;
                    int bufsize = 1024 * 1024 * 10;
                    char * buf = (char *)malloc(bufsize);
                    memset(buf, 0, bufsize);
                    long countbufsize = 0;

                    while (nRead = RTMP_Read(rtmp, buf, bufsize)) {
                        fwrite(buf, 1, nRead, fp);

                        countbufsize += nRead;
                        RTMP_LOG("Receive: %5dByte, Total: %5.2fkB\n", nRead,
                                countbufsize * 1.0 / 1024);
                    }

                    //关闭文件流
                    fclose(fp);
                    free(buf);
                    receiveResult = 1;
                } else {
                    RTMP_LOG("open file fail.");
                }
                RTMP_Close(rtmp);
            } else {
                RTMP_LOG("connect stream fail.");
            }
        } else {
            RTMP_LOG("connect url fail.");
        }
    } else {
        RTMP_LOG("set up  url fail.");
    }

    RTMP_Free(rtmp);
    rtmp = nullptr;
    RTMP_LOG("rtmp receive finish.");

    env->ReleaseStringUTFChars(host, url);
    env->ReleaseStringUTFChars(path, file);
    return receiveResult;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_checkAddressValidity(JNIEnv * env, jclass clazz, jstring host) {
    //分配一个RTMP
    RTMP * rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);
    const char * url = env->GetStringUTFChars(host, NULL);
    unsigned int port = 0;
    int ret = RTMP_ParseURL(url, &rtmp->Link.protocol, &rtmp->Link.hostname,
            &port, &rtmp->Link.playpath0, &rtmp->Link.app);
    env->ReleaseStringUTFChars(host, url);
    RTMP_Free(rtmp);
    rtmp = nullptr;
    return ret;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_allocRtmp(JNIEnv *env, jclass clazz) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    bool result = rtmp;
    RTMP_Free(rtmp);
    rtmp = nullptr;
    return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_initRtmp(JNIEnv *env, jclass clazz) {
    RTMP *rtmp = RTMP_Alloc();
    RTMP_Init(rtmp);
    bool result = rtmp;
    RTMP_Free(rtmp);
    rtmp = nullptr;
    return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_setupUrl(JNIEnv *env, jclass clazz, jstring host) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char *url = env->GetStringUTFChars(host, NULL);

    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *) url);
    RTMP_Free(rtmp);
    rtmp = nullptr;
    return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_connect(JNIEnv *env, jclass clazz, jstring host) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char *url = env->GetStringUTFChars(host, NULL);

    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *) url);
    if (result) {
        //表示直播流
        rtmp->Link.lFlags |= RTMP_LF_LIVE;
        //一个小时
        RTMP_SetBufferMS(rtmp, 3600 * 1000);
        //连接服务器
        result = RTMP_Connect(rtmp, NULL);
    }

    RTMP_Free(rtmp);
    rtmp = nullptr;
    return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_connectStream(JNIEnv *env, jclass clazz, jstring host) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char *url = env->GetStringUTFChars(host, NULL);

    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *) url);
    if (result) {
        //表示直播流
        rtmp->Link.lFlags |= RTMP_LF_LIVE;
        //一个小时
        RTMP_SetBufferMS(rtmp, 3600 * 1000);
        //连接服务器
        result = RTMP_Connect(rtmp, NULL);
        if (result) {
            //连接流
            result = RTMP_ConnectStream(rtmp, 0);
            RTMP_Close(rtmp);
        }
    }
    RTMP_Free(rtmp);
    rtmp = nullptr;
    return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_enableWrite(JNIEnv *env, jclass clazz, jstring host) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char *url = env->GetStringUTFChars(host, NULL);

    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *) url);
    if (result) {
        //开启写入，在connect之前调用
        RTMP_EnableWrite(rtmp);
    }
    return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_pause(JNIEnv *env, jclass clazz, jstring host, jboolean isPause) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char *url = env->GetStringUTFChars(host, NULL);

    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *) url);
    if (result) {
        //表示直播流
        rtmp->Link.lFlags |= RTMP_LF_LIVE;
        //一个小时
        RTMP_SetBufferMS(rtmp, 3600 * 1000);
        //连接服务器
        result = RTMP_Connect(rtmp, NULL);
        if (result) {
            //连接流
            result = RTMP_ConnectStream(rtmp, 0);

            int nRead;
            int bufsize = 1024 * 1024 * 10;
            char *buf = (char *) malloc(bufsize);
            memset(buf, 0, bufsize);
            long countbufsize = 0;
            result = 0;
            while (nRead = RTMP_Read(rtmp, buf, bufsize)) {
                countbufsize += nRead;
                result = RTMP_Pause(rtmp, isPause);
                RTMP_LOG("Receive: %5dByte, Total: %5.2fkB\n", nRead,
                        countbufsize * 1.0 / 1024);
                break;
            }
            RTMP_Close(rtmp);
        }
    }
    RTMP_Free(rtmp);
    rtmp = nullptr;
    return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_seek(JNIEnv *env, jclass clazz, jstring host) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char *url = env->GetStringUTFChars(host, NULL);

    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *) url);
    if (result) {
        //表示直播流
        rtmp->Link.lFlags |= RTMP_LF_LIVE;
        //一个小时
        RTMP_SetBufferMS(rtmp, 3600 * 1000);
        //连接服务器
        result = RTMP_Connect(rtmp, NULL);
        if (result) {
            //连接流
            result = RTMP_ConnectStream(rtmp, 0);

            int nRead;
            int bufsize = 1024 * 1024 * 10;
            char *buf = (char *) malloc(bufsize);
            memset(buf, 0, bufsize);
            long countbufsize = 0;

            bool isSeek = FALSE;
            result=0;
            while (nRead = RTMP_Read(rtmp, buf, bufsize)) {
                countbufsize += nRead;
                RTMP_LOG("Receive: %5dByte, Total: %5.2fkB\n", nRead,
                        countbufsize * 1.0 / 1024);
                if (!isSeek) {
                    result = RTMP_SendSeek(rtmp, 0);
                    isSeek = TRUE;
                    break;
                }
            }
            RTMP_Close(rtmp);
        }
    }
    RTMP_Free(rtmp);
    rtmp = nullptr;
    return result;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_freeTest(JNIEnv *env, jclass clazz) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    RTMP_Free(rtmp);
    rtmp = nullptr;
    bool result = rtmp;
    return !result;
}

JNIEXPORT jboolean JNICALL
Java_com_rtmp_librtmp_RtmpUtil_read(JNIEnv *env, jclass clazz, jstring host) {
    //分配一个RTMP
    RTMP *rtmp = RTMP_Alloc();
    //初始化
    RTMP_Init(rtmp);

    const char *url = env->GetStringUTFChars(host, NULL);

    //设置RTMP服务器
    int result = RTMP_SetupURL(rtmp, (char *) url);
    if (result) {
        //表示直播流
        rtmp->Link.lFlags |= RTMP_LF_LIVE;
        //一个小时
        RTMP_SetBufferMS(rtmp, 3600 * 1000);
        //连接服务器
        result = RTMP_Connect(rtmp, NULL);
        if (result) {
            //连接流
            result = RTMP_ConnectStream(rtmp, 0);

            int nRead;
            int bufsize = 1024 * 1024 * 10;
            char *buf = (char *) malloc(bufsize);
            memset(buf, 0, bufsize);
            long countbufsize = 0;

            nRead = RTMP_Read(rtmp, buf, bufsize);
            result = nRead;
            RTMP_Close(rtmp);
        }
    }
    RTMP_Free(rtmp);
    rtmp = nullptr;
    return result;
}